import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
/*
 * RSA主程序，用于产生秘钥，加密解密，签名验签等。
 * */
public class RSA {
     BigInteger ZERO= BigInteger.ZERO;
     BigInteger ONE= BigInteger.ONE;
    private BigInteger p,q,fn,n,k,d;
    private LargePrime largePrime=new LargePrime();
    private BigInteger e=new BigInteger("65537");
    private int bits=15;
    /**
     * 构造函数，构建秘钥并初始化
     * @param bits 秘钥长度
     * */
    public RSA(int bits){
        this.bits=bits;
        makeKeys();
    }
    /**
     * 构造函数,不构造秘钥
     * */
    public RSA(){
    }
    /**
     * 构造函数,设置秘钥
     * */
    public RSA(PublicKey publicKey,PrivateKey privateKey){
        e=publicKey.getE();
        n=publicKey.getN();
        d=privateKey.getD();
    }

    /**
     * 设置PublicKey
     * */
    public void setPublicKey(PublicKey publicKey){
        e=publicKey.getE();
        n=publicKey.getN();
    }
    /**
     * 设置PrivateKey
     * */
    public void setPrivateKey(PrivateKey privateKey){
        d=privateKey.getD();
        n=privateKey.getN();
    }
    /**
     * 初始化
     * */
    private void makeKeys(){
         p = LargePrime.makePrime(bits);
         q = LargePrime.makePrime(bits);
         e=LargePrime.makePrime(bits-1);

         fn=p.subtract(ONE).multiply(q.subtract(ONE));
         n=p.multiply(q);
         k=getk(fn,e);//e.multiply(new BigInteger(""+((int)(Math.random()*10))));//+(int)Math.random()*1000000);
         d=k.multiply(fn).add(ONE).divide(e);
        System.out.println("p="+p);
        System.out.println("q="+q);
        System.out.println("n="+n);
        System.out.println("fn="+fn);
        System.out.println("d="+d);
        System.out.println("公钥："+getPublicKey());
        System.out.println("私钥："+getPrivateKey());
        System.out.println("秘钥位数："+bits+" bit");

        BigInteger data = new BigInteger("1234");
        while (!data.equals(decrypt(encrypt(data)))){
            System.out.println("秘钥有误，更新中...");
            makeKeys();
        }
    }
    /**
     * 快速幂
     * @param base 底数
     * @param power 指数
     * @param n 结果 mod n
     * @return 计算结果
     * */
    public BigInteger normalPower(BigInteger base,BigInteger power,BigInteger n){
        BigInteger result=new BigInteger("1");
        while(power.compareTo(ZERO)==1){
            if(power.and(ONE).compareTo(ONE)==0) result=result.multiply(base).mod(n);
            power=power.shiftRight(1);
            base=base.multiply(base).mod(n);
        }
        return result;
    }

    /**
     * 获取k值，计算d用
     * @param fn
     * @param e
     * @return 计算结果
     * */
     BigInteger getk(BigInteger fn,BigInteger e){
        BigInteger k = new BigInteger("2");
        while (true){
            if(k.multiply(fn).add(ONE).mod(e).compareTo(ZERO)==0)
                return k;
            k=k.add(ONE);
        }
    }
    public PublicKey getPublicKey(){
         return new PublicKey(e,n);
    }
    public PrivateKey getPrivateKey(){
        return new PrivateKey(d,n);
    }
    /**
     * 加密
     * @param data 加密数据，一个字符
     * @return 密文
     * */
    public BigInteger encrypt(BigInteger data) {
        return normalPower(data,e,n).mod(n);
    }
    /**
     * 加密
     * @param plaintext 加密数据，一个字符串
     * @return 密文
     * */
    public Ciphertext encrypt(Plaintext plaintext) {
        Ciphertext ciphertext = new Ciphertext();
        for (BigInteger data :plaintext.datas){
            ciphertext.add(encrypt(data));
        }
//        stringBuffer.append(encrypt(new BigInteger(""+(int) i)));
        return ciphertext;
    }
    /**
     * 解密
     * @param data 待解密数据，一个字符
     * @return 明文
     * */
    public BigInteger decrypt(BigInteger data) {
        return normalPower(data, d, n).mod(n);
    }
    /**
     * 解密
     * @param ciphertext 待解密数据，一堆
     * @return 明文
     * */
    public Plaintext decrypt(Ciphertext ciphertext) {
        Plaintext plaintext=new Plaintext();
        for (BigInteger data : ciphertext.getMsgs()){
            plaintext.add(decrypt(data));
        }
        return plaintext;
    }
    /**
     * 签名
     * @param plaintext 待签名数据
     * @return 私钥加密后的密文
     * */
    public Ciphertext signature(Plaintext plaintext) {
        Ciphertext ciphertext=new Ciphertext();
        for (BigInteger data : plaintext.getMsgs()){
            ciphertext.add(decrypt(data));
        }
        return ciphertext;
    }
    /**
     * 验证签名
     * @param ciphertext 签名数据
     * @return 私钥加密后的密文
     * */
    public Plaintext VerifySignature(Ciphertext ciphertext) {
        Plaintext plaintext=new Plaintext();
        for (BigInteger data : ciphertext.getMsgs()){
            plaintext.add(encrypt(data));
        }
        return plaintext;
    }

}
